package com.billlv.codegenerator.common.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class RedisUtility<T> {

    @Autowired
    private RedisTemplate<String, String> redisTemplate; // 使用 String 类型存储对象
    private static final ObjectMapper objectMapper = new ObjectMapper();
    /**
     * Save an object to Redis with a TTL (Time to Live).
     *
     * @param key   the key
     * @param value the value
     * @param ttl   the TTL in seconds
     */
    public void save(String key, T value, long ttl) {
        try {
            String serializedValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForValue().set(key, serializedValue, Duration.ofSeconds(ttl));
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and save to Redis", e);
        }
    }


    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }



    /**
     * Save an object to Redis without TTL.
     *
     * @param key   the key
     * @param value the value
     */
    public void save(String key, T value) {
        try {
            String serializedValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForValue().set(key, serializedValue);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and save to Redis", e);
        }
    }

    /**
     * Set a TTL for a key.
     *
     * @param key the key
     * @param ttl the TTL in seconds
     */
    public void setTTL(String key, long ttl) {
        redisTemplate.expire(key, ttl, TimeUnit.SECONDS);
    }

    /**
     * Retrieve an object from Redis by key.
     *
     * @param key the key
     * @param clazz the class of the object
     * @return the object, or null if not found
     */
    public T get(String key, Class<T> clazz) {
        try {
            String serializedValue = redisTemplate.opsForValue().get(key);
            return serializedValue == null ? null : objectMapper.readValue(serializedValue, clazz);
        } catch (Exception e) {
            throw new RuntimeException("Failed to deserialize from Redis", e);
        }
    }

    /**
     * Delete a key from Redis.
     *
     * @param key the key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }




    /**
     * Push a single object to the end of a Redis list.
     *
     * @param key   the key
     * @param value the value
     */
    public void pushToList(String key, T value) {
        try {
            String serializedValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForList().rightPush(key, serializedValue);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and push to Redis list", e);
        }
    }

    /**
     * Push multiple objects to the end of a Redis list.
     *
     * @param key    the key
     * @param values the list of values
     */
    public void pushAllToList(String key, List<T> values) {
        try {
            List<String> serializedValues = values.stream()
                    .map(value -> {
                        try {
                            return objectMapper.writeValueAsString(value);
                        } catch (Exception e) {
                            throw new RuntimeException("Failed to serialize list item", e);
                        }
                    })
                    .collect(Collectors.toList());
            redisTemplate.opsForList().rightPushAll(key, serializedValues);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and push list to Redis", e);
        }
    }

    /**
     * Push multiple objects to the end of a Redis list with TTL.
     *
     * @param key    the key
     * @param values the list of values
     * @param ttl    the TTL in seconds
     */
    public void pushAllToListWithTTL(String key, List<T> values, long ttl) {
        pushAllToList(key, values);
        redisTemplate.expire(key, ttl, TimeUnit.SECONDS);
    }

     /**
     * Retrieve a list of objects from Redis by key.
     *
     * @param key the key
     * @param typeReference the type reference for the list
     * @return the list of objects, or null if not found
     */
    public List<T> getList(String key, TypeReference<List<T>> typeReference) {
        try {
            String serializedValue = redisTemplate.opsForValue().get(key);
            return serializedValue == null ? null : objectMapper.readValue(serializedValue, typeReference);
        } catch (Exception e) {
            throw new RuntimeException("Failed to deserialize list from Redis", e);
        }
    }

    /**
     * Remove and return the first element from a Redis list.
     *
     * @param key the key
     * @param clazz the class of the object
     * @return the object, or null if the list is empty
     */
    public T popFromList(String key, Class<T> clazz) {
        try {
            String serializedValue = redisTemplate.opsForList().leftPop(key);
            return serializedValue == null ? null : objectMapper.readValue(serializedValue, clazz);
        } catch (Exception e) {
            throw new RuntimeException("Failed to deserialize popped item from Redis", e);
        }
    }

    /**
     * Get the size of a Redis list.
     *
     * @param key the key
     * @return the size of the list
     */
    public Long getListSize(String key) {
        return redisTemplate.opsForList().size(key);
    }



    /**
     * Save a hash map to Redis with TTL.
     *
     * @param key the key
     * @param map the hash map
     * @param ttl the TTL in seconds
     */
    public void saveHashWithTTL(String key, Map<String, T> map, long ttl) {
        try {
            Map<String, String> serializedMap = map.entrySet().stream()
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            entry -> {
                                try {
                                    return objectMapper.writeValueAsString(entry.getValue());
                                } catch (Exception e) {
                                    throw new RuntimeException("Failed to serialize map entry", e);
                                }
                            }
                    ));
            redisTemplate.opsForHash().putAll(key, serializedMap);
            redisTemplate.expire(key, ttl, TimeUnit.SECONDS);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and save hash map to Redis", e);
        }
    }

    /**
     * Save a hash map to Redis.
     *
     * @param key the key
     * @param map the hash map
     */
    public void saveHash(String key, Map<String, T> map) {
        try {
            Map<String, String> serializedMap = map.entrySet().stream()
                    .collect(Collectors.toMap(
                            Map.Entry::getKey,
                            entry -> {
                                try {
                                    return objectMapper.writeValueAsString(entry.getValue());
                                } catch (Exception e) {
                                    throw new RuntimeException("Failed to serialize map entry", e);
                                }
                            }
                    ));
            redisTemplate.opsForHash().putAll(key, serializedMap);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and save hash map to Redis", e);
        }
    }


    /**
     * Update a single item in a Redis hash.
     *
     * @param key   the key of the hash
     * @param field the field to update
     * @param value the value to set
     * @param <T>   the type of the value
     */
    public <T> void updateHashItem(String key, String field, T value) {
        try {
            redisTemplate.opsForHash().put(key, field, objectMapper.writeValueAsString(value));
        } catch (Exception e) {
            throw new RuntimeException("Failed to update hash item in Redis", e);
        }
    }


    /**
     * Retrieve a hash map from Redis.
     *
     * @param key the key
     * @param clazz the class of the map values
     * @return the hash map
     */
    public Map<String, T> getHash(String key, Class<T> clazz) {
        try {
            Map<Object, Object> serializedMap = redisTemplate.opsForHash().entries(key);
            return serializedMap.entrySet().stream()
                    .collect(Collectors.toMap(
                            entry -> (String) entry.getKey(),
                            entry -> {
                                try {
                                    return objectMapper.readValue((String) entry.getValue(), clazz);
                                } catch (Exception e) {
                                    throw new RuntimeException("Failed to deserialize map entry", e);
                                }
                            }
                    ));
        } catch (Exception e) {
            throw new RuntimeException("Failed to deserialize hash map from Redis", e);
        }
    }


    /**
     * Retrieve a list of values from a Redis hash.
     *
     * @param key   the key of the hash
     * @param clazz the class of the values
     * @param <T>   the type of the values
     * @return a list of deserialized values
     */
    public <T> List<T> getHashValues(String key, Class<T> clazz) {
        try {
            Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
            return entries.values().stream()
                    .map(value -> {
                        try {
                            return objectMapper.readValue(value.toString(), clazz);
                        } catch (Exception e) {
                            throw new RuntimeException("Failed to deserialize hash value", e);
                        }
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            throw new RuntimeException("Failed to retrieve hash values from Redis", e);
        }
    }



    /**
     * Delete a field from a hash in Redis.
     *
     * @param key   the key
     * @param field the field to delete
     */
    public void deleteHashField(String key, String field) {
        redisTemplate.opsForHash().delete(key, field);
    }




    /**
     * Add an item to a Redis sorted set with a score.
     */
    public void addToZSet(String key, T value, double score) {
        try {
            String jsonValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForZSet().add(key, jsonValue, score);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Error serializing object to JSON", e);
        }
    }

    /**
     * Retrieve items from a Redis sorted set within a score range.
     */
    public List<T> getZSet(String key, double minScore, double maxScore, Class<T> clazz) {
        Set<String> jsonSet = redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
        if (jsonSet == null) {
            return Collections.emptyList();
        }
        return jsonSet.stream()
                .map(json -> {
                    try {
                        return objectMapper.readValue(json, clazz);
                    } catch (JsonProcessingException e) {
                        throw new RuntimeException("Error deserializing JSON to object", e);
                    }
                })
                .collect(Collectors.toList());
    }

    /**
     * Add multiple items to a Redis sorted set with scores.
     */
    public void addAllToZSet(String key, Map<T, Double> values) {
        Set<ZSetOperations.TypedTuple<String>> tuples = values.entrySet().stream()
                .map(entry -> {
                    try {
                        String jsonValue = objectMapper.writeValueAsString(entry.getKey());
                        return new DefaultTypedTuple<>(jsonValue, entry.getValue());
                    } catch (JsonProcessingException e) {
                        throw new RuntimeException("Error serializing object to JSON", e);
                    }
                })
                .collect(Collectors.toSet());
        redisTemplate.opsForZSet().add(key, tuples);
    }


    /**
     * Add a single element to a Redis Set.
     *
     * @param key   the key
     * @param value the value
     */
    public void addToSet(String key, T value) {
        try {
            String serializedValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForSet().add(key, serializedValue);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize and add to Redis Set", e);
        }
    }


    /**
     * Add multiple elements to a Redis Set.
     *
     * @param key    the key
     * @param values the list of values
     */
    public void addAllToSet(String key, List<T> values) {
        try {
            List<String> serializedValues = values.stream()
                    .map(value -> {
                        try {
                            return objectMapper.writeValueAsString(value);
                        } catch (JsonProcessingException e) {
                            throw new RuntimeException("Failed to serialize list item", e);
                        }
                    })
                    .collect(Collectors.toList());
            redisTemplate.opsForSet().add(key, serializedValues.toArray(new String[0]));
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize and add list to Redis Set", e);
        }
    }


    /**
     * Remove a single element from a Redis Set.
     *
     * @param key   the key
     * @param value the value to remove
     */
    public void removeFromSet(String key, T value) {
        try {
            String serializedValue = objectMapper.writeValueAsString(value);
            redisTemplate.opsForSet().remove(key, serializedValue);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize and remove from Redis Set", e);
        }
    }


    /**
     * Retrieve all elements from a Redis Set.
     *
     * @param key   the key
     * @param clazz the class of the set elements
     * @return the list of elements
     */
    public Set<T> getSet(String key, Class<T> clazz) {
        try {
            Set<String> serializedValues = redisTemplate.opsForSet().members(key);
            if (serializedValues == null) {
                return Collections.emptySet();
            }
            return serializedValues.stream()
                    .map(value -> {
                        try {
                            return objectMapper.readValue(value, clazz);
                        } catch (JsonProcessingException e) {
                            throw new RuntimeException("Failed to deserialize Set element", e);
                        }
                    })
                    .collect(Collectors.toSet());
        } catch (Exception e) {
            throw new RuntimeException("Failed to retrieve and deserialize Redis Set", e);
        }
    }


    /**
     * Check if a Redis Set contains a specific element.
     *
     * @param key   the key
     * @param value the value to check
     * @return true if the set contains the element, false otherwise
     */
    public boolean isMemberOfSet(String key, T value) {
        try {
            String serializedValue = objectMapper.writeValueAsString(value);
            Boolean isMember = redisTemplate.opsForSet().isMember(key, serializedValue);
            return isMember != null && isMember;
        } catch (JsonProcessingException e) {
            throw new RuntimeException("Failed to serialize and check Redis Set membership", e);
        }
    }
    /**
     * Delete a key from Redis.
     *
     * @param key the key to be deleted
     */
    public void del(String key) {
        try {
            redisTemplate.delete(key);
        } catch (Exception e) {
            throw new RuntimeException("Failed to delete key from Redis", e);
        }
    }



}
